#ifndef __SSHFS_H__
#define __SSHFS_H__

#include "libssh2.h"
#include "libssh2_publickey.h"
#include "libssh2_sftp.h"
#include "provider_api.h"
#include "configuration.h"
#include "utils.h"
#include "errors.h"
#include "timer.h"

class Libssh2File;
class Libssh2Cache;


#define LIBSSH2_SFTP_OPENEITHER 2

class Libssh2Fs : public FilesystemProvider
{
public:
	Libssh2Fs(int fd, const Configuration& cfg, UserInterface *ui);
	~Libssh2Fs();

	virtual int Start();
	virtual void Stop();

	int OpenFile(ProviderFile **f, ProviderDir **d, const std::string& path, OpenType type, bool create_ro, bool open_writable);
	int Rename(const std::string& from, const std::string& to, bool overwrite_existing);

	void GetDiskInfo(uint64_t& free_total, uint64_t& free_quota, uint64_t& total_quota);

	std::string GetFsType() { return "sshfs"; }

	struct SftpDirEntry
	{
		LIBSSH2_SFTP_ATTRIBUTES Attrib;
		char *Name;
		size_t NameLen;
		SftpDirEntry *Next;
	};

	enum SftpRequestType
	{
		SftpOpen,
		SftpClose,
		SftpRead,
		SftpWrite,
		SftpSetStat,
		SftpReadDir,
		SftpRmdir,
		SftpUnlink,
		SftpRename,
	};
	struct SftpRequest
	{
		SftpRequest *Next;
		SftpRequestType Type;

		union {
			struct {
				const char *Path;
				size_t PathLen;
				unsigned long Flags, Mode, Type;
				bool AutoStat;
				LIBSSH2_SFTP_HANDLE *Handle;
				LIBSSH2_SFTP_ATTRIBUTES Attr;
			} Open;
			struct {
				LIBSSH2_SFTP_HANDLE *Handle;
			} Close;
			struct {
				LIBSSH2_SFTP_HANDLE *Handle;
				void *Buffer;
				unsigned long Count;
				size_t Transferred;
			} IO;
			struct {
				LIBSSH2_SFTP_HANDLE *Handle;
				const char *Path;
				LIBSSH2_SFTP_ATTRIBUTES Attr;
			} SetStat;
			struct {
				LIBSSH2_SFTP_HANDLE *Handle;
				char *Buffer;
				size_t BufferSize;
				size_t Count;
				SftpDirEntry *FirstEntry;
			} ReadDir;
			struct {
				const char *From, *To;
				size_t FromLen, ToLen;
				bool Overwrite;
				bool Atomic;
				bool Native;
			} Rename;
			struct {
				const char *Path;
				size_t PathLen;
				bool IsDir;
			} Unlink;
		};
		NotificationEvent Evt;
		int Result;
	};

private:
	int CheckKnownFingerprint(unsigned char *sha, unsigned char *md5);

	//all SFTP communication must be restricted to a single thread
	//so all API usage is posted to here and waited on
	static void* SftpThread(void *arg) { ((Libssh2Fs*)arg)->SftpThread(); return 0; }
	void SftpThread();
	void BlockingRequest(SftpRequest *req)
	{
		req->Result = -1;
		Fifo.Queue(req);
		req->Evt.Wait();
	}

	bool CheckError(int err);
	bool CheckSftpError(int err);
	int GetError(std::string *errstring = NULL);

	static void KbdInteractiveAuth(const char* name, int name_len, const char* instruction,
		int instruction_len, int num_prompts,
		const LIBSSH2_USERAUTH_KBDINT_PROMPT* prompts,
		LIBSSH2_USERAUTH_KBDINT_RESPONSE* responses, void **abstract);

	ProducerConsumerFifo<SftpRequest> Fifo;
	pthread_t IoThread;
	std::string Name;
	UserInterface *UI;
	Configuration Config;
	LIBSSH2_SESSION *Session;
	LIBSSH2_SFTP *SFTP;
	int Fd;
	Libssh2Cache *Cache;
};

class Libssh2File : public ProviderFile
{
public:
	Libssh2File(const std::string& path, ProducerConsumerFifo<Libssh2Fs::SftpRequest>& fifo, LIBSSH2_SFTP_HANDLE *h, DirStatInfo& stat);
	~Libssh2File();

	virtual int Read(void *buffer, unsigned long long offset, unsigned long size, unsigned long& transferred);
	virtual int Write(void *buffer, unsigned long long offset, unsigned long size, unsigned long& transferred);
	virtual int Flush();
	virtual int Delete();
	virtual int Resize(unsigned long long size);

	virtual const DirStatInfo& Stat();

private:
	std::string Path;
	ProducerConsumerFifo<Libssh2Fs::SftpRequest>& Fifo;
	LIBSSH2_SFTP_HANDLE *Handle;
	DirStatInfo StatInfo;
};

class Libssh2Dir : public ProviderDir
{
public:
	Libssh2Dir(const std::string& path, ProducerConsumerFifo<Libssh2Fs::SftpRequest>& fifo, LIBSSH2_SFTP_HANDLE *h);
	~Libssh2Dir();

	virtual int ListFiles(FsListFunc cb, void *context);

	virtual const DirStatInfo& Stat();
	virtual int Delete();

private:
	void PopulateDirEnts();

	std::string Path;
	ProducerConsumerFifo<Libssh2Fs::SftpRequest>& Fifo;
	LIBSSH2_SFTP_HANDLE *Handle;
	DirStatInfo StatInfo;
	CriticalSection PopulateLock;

	struct DirEntList
	{
		void Free()
		{
			if(Next)
				Next->Free();
			free(this);
		}

		DirEntList *Next;
		Libssh2Fs::SftpDirEntry *First;
	};

	DirEntList *DirEnts;
};


class Libssh2Cache
{
public:
	void CacheAdd(const std::string& path, bool writable, ProviderCommon *obj);
	void CacheAdd(const std::string& path, int err);
	bool CacheLookup(const std::string& path, ProviderCommon*& obj, int& err, bool want_write);

private:
	struct CacheEntry : public GenericTimerCallback
	{
		CacheEntry(ProviderCommon *obj, bool writable)
			: Obj(obj), Writable(writable), Error(0), Timer(0)
		{
		}

		CacheEntry(int err)
			: Obj(0), Writable(false), Error(err), Timer(0)
		{
		}

		~CacheEntry()
		{
			delete Timer;
		}

		void TimerExpired();

		ProviderCommon *Obj;
		bool Writable;
		int Error;
		std::map<std::string, CacheEntry>::iterator Self;
		Libssh2Cache *Parent;
		GenericTimer *Timer;
	};

	CriticalSection CacheLock;
	std::map<std::string, CacheEntry> Cache;
};


#endif
